#include "httprequest.h"
using namespace std;

//一个set，用来存储默认的html文件
const unordered_set<string> HttpRequest::DEFAULT_HTML
{
    "/index","register","/login","/welcome","/video","/picture",
};
//默认HTML文件与标签ID的映射
const unordered_map<string,int> HttpRequest::DEFAULT_HTML_TAG
{
    {"/register.html",0},{"/login.html",1},
};
//Init()函数，初始化HTTP请求对象
void HttpRequest::Init()
{
    //将HTTP请求方法、路径、版本、正文内容清空
    method_ = path_ = version_ = body_ = "";
    //将HTTP请求状态设置为请求行
    state_ = REQUEST_LINE;
    //将HTTP请求头的哈希表清空
    header_.clear();
    //将post请求参数的哈希表清空
    post_.clear();
}
//IsKeepAlive()函数，判断HTTP请求是否保持连接
bool HttpRequest::IsKeepAlive() const 
{
    //如果HTTP请求头中存在"Connection"字段
    if(header_.count("Connection") == 1)
    {
        //返回HTTP请求头中的"Connection"字段的值是否为"keep-alive"，并且HTTP版本为"1.1"
        return header_.find("Connection")->second == "keep-alive" && version_ == "1.1";
    }
    //如果HTTP请求头中不存在"Connection"字段
    //返回false
    return false;
}
//parse()函数，解析HTTP请求
bool HttpRequest::parse(Buffer& buff)
{
    //CRLF是HTTP请求的结束标志
    const char CRLF[] = "\r\n";
    //如果HTTP请求的可读字节数小于等于0（也就是说缓冲区中没有数据）
    if(buff.ReadableBytes() <= 0)
    {
        //返回false
        return false;
    }
    //循环判断缓冲区中是否有可读字节并且状态机不是FINISH
    while(buff.ReadableBytes() && state_ != FINISH)
    {
        //不是，进入循环内部
        //查找CRLF结束符
        const char* lineEnd = search(buff.Peek(),buff.BeginWriteConst(),CRLF,CRLF+2);
        //构建line，将buff.Peek()指向的内容复制到line中，复制的长度为lineEnd - buff.Peek()
        std::string line(buff.Peek(),lineEnd);
        //选择状态机状态
        switch(state_)
        {
            //状态机如果是REQUEST_LINE(请求行解析状态)
            case REQUEST_LINE:
            {
                //如果请求行解析失败
                if(!ParseRequestLine_(line))
                {
                    //返回false
                    return false;
                }
                //如果请求行解析成功
                //解析路径
                ParsePath_();
                break;
            }
            //如果状态机是HEADERS(请求头解析状态)
            case HEADERS:
            {
                //解析请求头
                ParseHeader_(line);
                //如果缓冲区可读字节数小于等于2
                if(buff.ReadableBytes() <= 2)
                {
                    //如果缓冲区可读字节数小于等于2
                    //将状态机设置为FINISH
                    state_ = FINISH;
                }
                break;
            }
            //如果状态机是BODY(请求体解析状态)
            case BODY:
            {
                //解析请求体
                ParseBody_(line);
                break;
            }
            default:
                break;
        }
        //如果lineEnd指向的位置是缓冲区，可读区域的末尾（说明缓冲区中的HTTP报文已经解析完毕）
        if(lineEnd == buff.BeginWriteConst())
        {
            //结束循环
            break;
        }
        //将缓冲区中的，已经处理完毕的数据，从缓冲区中移除
        buff.RetrieveUntil(lineEnd+2);
    }
    //打印日志，输出请求方法、路径、版本
    LOG_DEBUG("[%s], [%s], [%s]", method_.c_str(), path_.c_str(), version_.c_str());
    //HTTP请求已经解析完毕，返回true
    return true;
}
//ParsePath_()函数，解析请求路径
void HttpRequest::ParsePath_()
{
    //如果请求路径是"/"
    if(path_ == "/")
    {
        //如果请求路径是"/"，则将请求路径设置为"/index.html"
        path_ = "/index.html";
    }
    //如果请求路径不是"/"
    else
    {
        //遍历默认HTML文件
        for(auto& item:DEFAULT_HTML)
        {
            //如果请求路径是默认HTML文件中的一个
            if(item == path_)
            {
                //如果请求路径是默认HTML文件中的一个，在路径后面添加".html"
                path_ += ".html";
                //结束循环
                break;
            }
        }
    }
}
//ParseRequestLine_()函数，解析请求行
bool HttpRequest::ParseRequestLine_(const string& line)
{
    //正则表达式对象，第一个分组（([^ ]*)）匹配请求方法，第二个分组（([^ ]*)）匹配请求路径，第三个分组（([^ ]*)）匹配HTTP版本
    regex patten("^([^ ]*) ([^ ]*) HTTP/([^ ]*)$");
    //smatch对象，用来存储正则表达式匹配的结果
    smatch subMatch;
    //如果正则表达式匹配成功（从line里，按照patten匹配，放到subMatch）
    if(regex_match(line,subMatch,patten))
    {
        //将请求方法、路径、版本赋值给成员变量
        method_ = subMatch[1];
        path_ = subMatch[2];
        version_ = subMatch[3];
        //将状态机设置为HEADERS（该处理HTTP报文的头部了）
        state_ = HEADERS;
        //返回true
        return true;
    }
    //输出错误日志
    LOG_ERROR("RequestLine Error");
    //返回false
    return false;
}
//ParseHeader_()函数，解析请求头
void HttpRequest::ParseHeader_(const string& line)
{
    //定义正则表达式对象，第一个分组（([^:]*):）匹配请求头的字段名，第二个分组（(.*)$）匹配请求头的字段值
    regex patten("^([^:]*): ?(.*)$");
    //定义smatch对象，用来存储正则表达式匹配的结果
    smatch subMatch;
    //如果正则表达式匹配成功（从line里，按照patten匹配，放到subMatch）
    if(regex_match(line,subMatch,patten))
    {
        //将请求头的字段名和字段值，存储到header_中
        header_[subMatch[1]] = subMatch[2];
    }
    //如果匹配失败
    else
    {
        //将状态机设置为BODY（该处理HTTP报文的请求体了）
        state_ = BODY;
    }
}
//ParseBody_()函数，解析请求体
void HttpRequest::ParseBody_(const string& line)
{
    //将请求体赋值给body_
    body_ = line;
    //解析请求体
    ParsePost_();
    //将状态机设置为FINISH（缓冲区里面的报文处理完毕）
    state_ = FINISH;
    //输出报文体里面的信息
    LOG_DEBUG("Body: %s,len:%d", body_.c_str(),line.size());
}
//将对应字母转换成数字的函数
int HttpRequest::ConverHex (char ch)
{
    if(ch >= 'A' && ch <= 'F') return ch-'A' + 10;
    if(ch >= 'a' && ch <= 'f') return ch-'a' + 10;
    return ch;
}
//解析POST请求体的函数
void HttpRequest::ParsePost_()
{
    //如果请求方法是POST，并且请求头的Content-Type是application/x-www-form-urlencoded
    if(method_ == "POST" && header_["Content-Type"] == "application/x-www-form-urlencoded")
    {
        //解析application/x-www-form-urlencoded的报文
        ParseFromUrlencoded_();
        //如果请求路径是默认HTML文件中的一个
        if(DEFAULT_HTML_TAG.count(path_))
        {
            //获取请求路径对应的HTML文件标签
            int tag = DEFAULT_HTML_TAG.find(path_)->second;
            //输出标签信息
            LOG_DEBUG("Tag:%d",tag);
            //如果标签是0或者标签是1（要访问的页面是登录页或者注册页）
            if(tag == 0 || tag == 1)
            {
                //如果是登录页
                bool isLogin = (tag == 1);
                //进行用户验证，验证用户名和密码是否正确
                if(UserVerify(post_["username"],post_["password"],isLogin))
                {
                    //验证成功
                    //进入欢迎页面
                    path_ = "/welcome.html";
                }
                //用户认证失败
                else
                {
                    //弹出错误页面
                    path_ = "/error.html";
                }
            }
        }
    }
}
//解析application/x-www-form-urlencoded的报文
void HttpRequest::ParseFromUrlencoded_()
{
    //如果请求体为空，直接返回
    if(body_.size() == 0){return;}
    //定义一个字符串key，一个字符串value，用于存储键值对
    string key,value;
    //定义一个整数num，用于存储转换后的十六进制数
    int num = 0;
    //定义一个整数n，用于存储请求体的长度
    int n = body_.size();
    //定义一个整数i，用于遍历请求体
    //定义一个整数j，用于记录键值对的起始位置
    int i = 0,j = 0;
    //创建一个新的字符串用于构建解析后的内容，避免直接修改原字符串
    string newBody;
    newBody.reserve(n); // 预分配空间
    
    //遍历请求体
    for(;i < n;i++)
    {
        //ch用来存储当前遍历到的字符
        char ch = body_[i];
        //判断当前ch是什么
        switch(ch)
        {
            //ch如果是‘=’，说明是键值对的分隔符
            case '=':
            {
                //如果是‘=’，说明是键值对的分隔符
                //将j到i-1的子字符串赋值给key
                key = newBody.substr(j, i-j);
                newBody += '=';
                //将i+1赋值给j，j指向i的下一个位置
                j = i+1;
                break;
            }
            //ch如果是‘+’，说明是空格
            case '+':
            {
                //如果是‘+’，说明是空格
                //将空格添加到新的字符串中
                newBody += ' ';
                break;
            }
            //ch如果是‘%’，说明是十六进制数
            case '%':
            {
                //如果是%，说明是十六进制数，将%后面的两个字符从16进制转换为10进制
                if(i+2 < n) { // 边界检查
                    // 安全地转换十六进制字符
                    char c1 = body_[i+1];
                    char c2 = body_[i+2];
                    
                    // 正确的十六进制转换
                    int digit1 = (c1 >= 'A' && c1 <= 'F') ? (c1 - 'A' + 10) : 
                                (c1 >= 'a' && c1 <= 'f') ? (c1 - 'a' + 10) : 
                                (c1 >= '0' && c1 <= '9') ? (c1 - '0') : 0;
                    int digit2 = (c2 >= 'A' && c2 <= 'F') ? (c2 - 'A' + 10) : 
                                (c2 >= 'a' && c2 <= 'f') ? (c2 - 'a' + 10) : 
                                (c2 >= '0' && c2 <= '9') ? (c2 - '0') : 0;
                    
                    num = digit1 * 16 + digit2;
                    // 将解码后的字符添加到新字符串中
                    newBody += static_cast<char>(num);
                    // 将i跳过%和后面的两个字符
                    i += 2;
                } else {
                    // 如果没有足够的字符，就直接添加%符号
                    newBody += '%';
                }
                break;
            }
            //ch如果是‘&’，说明是键值对的结束符
            case '&':
            {
                //如果是‘&’，说明是键值对的结束符
                //将j到i-1的子字符串赋值给value
                value = newBody.substr(j, i-j);
                //将key和value存储到post_中
                post_[key] = value;
                //输出键值对
                LOG_DEBUG("%s = %s",key.c_str(),value.c_str());
                newBody += '&';
                //将i+1的值赋值给j
                j = i+1;
                break;
            }
            default:
            {
                //其他字符直接添加到新字符串中
                newBody += ch;
                break;
            }
        }
    }
    //如果j<=i，说明还有键值对没有解析
    assert(j <= i);
    //如果post_中没有key，并且j<i，说明还有键值对没有解析
    if(post_.count(key) == 0 && j < i)
    {
        //给value赋值
        value = newBody.substr(j, i-j);
        //将value赋值给post_中的key
        post_[key] = value;
    }
    
    // 用新构建的安全字符串替换原body_
    body_ = newBody;
}
//用户验证函数
bool HttpRequest::UserVerify(const string& name,const string& pwd,bool isLogin)
{
    //如果用户名或者密码为空，返回false
    if(name == "" || pwd == ""){return false;}
    //输出日志
    LOG_INFO("Verify name:%s, pwd:%s", name.c_str(), pwd.c_str());
    //创建一个MYSQL对象
    MYSQL* sql;
    //从连接池获取一个连接，sql指向这个新连接
    SqlConnRAII(&sql,SqlConnPool::Instance());
    //断言新连接是否建立成功
    assert(sql);
    //定义一个flag，false是登录，true是注册
    bool flag = false;
    //定义一个整数j，用于存储查询结果的字段数
    unsigned int j = 0;
    //定义一个字符数组order，用于存储SQL语句
    char order[256] = {0};
    //定义一个fields，用于存储查询结果的字段
    MYSQL_FIELD* fields = nullptr;
    //定义一个MYSQL_RES*类型的指针res，用于存储查询结果
    MYSQL_RES* res = nullptr;
    //如果不是登录，说明是注册，flag设为true
    if(!isLogin) {flag = true;}
    //向order中写入SQL语句，用来从数据库中查询用户名和密码
    snprintf(order,256,"SELECT username,password FROM user WHERE username='%s' LIMIT 1",name.c_str());
    //从日志输出SQL语句
    LOG_DEBUG("%s",order);
    //执行SQL语句
    if(mysql_query(sql,order))
    {
        //进入if内部，说明查询失败，释放结果集
        //mysql_free_result(res);
        //返回false
        return false;
    }
    //从日志输出查询结果
    res = mysql_store_result(sql);
    //从日志输出查询结果的字段数
    j = mysql_num_fields(res);
    //让fields指向查询结果的字段
    fields = mysql_fetch_fields(res);
    //在循环里定义一个MYSQL_ROW类型的指针row，用于存储查询结果的行
    while(MYSQL_ROW row = mysql_fetch_row(res))
    {
        //从日志输出查询结果的行
        LOG_DEBUG("MYSQL ROW: %s %s",row[0],row[1]);
        //从查询结果的行中取出密码
        string password(row[1]);
        //如果是登录
        if(isLogin)
        {
            //如果密码正确
            if(pwd == password) 
            {
                //如果密码正确，flag设为true
                flag = true;
            }
            //如果密码错误
            else
            {
                //如果密码错误，flag设为false
                flag = false;
                //如果密码错误，输出日志
                LOG_DEBUG("pwd error!");
            }
        }
        //如果是注册
        else
        {
            //将flag改为false
            flag = false;
            //如果用户名已存在，输出日志
            LOG_DEBUG("user used!");
        }
    }
    //释放结果集
    mysql_free_result(res);
    //如果不是登录，并且flag为true，说明注册成功
    if(!isLogin && flag == true)
    {
        //输出注册成功的日志
        LOG_DEBUG("regirstwer!");
        //清空order
        bzero(order,256);
        //向order中写入SQL语句，用来向数据库中插入用户名和密码
        snprintf(order,256,"INSERT INTO user(username, password) VALUES('%s','%s')",name.c_str(),pwd.c_str());
        //输出SQL语句
        LOG_DEBUG("%s",order);
        //查询一下，检测是否插入成功
        if(mysql_query(sql,order))
        {
            //进入if内部，说明插入失败，向日志输出插入失败
            LOG_DEBUG("Insert error!");
            //将flag改为false
            flag = false;
        }
        //如果插入成功，将falg改为true
        flag = true;
    }
    //如果插入成功，释放连接
    SqlConnPool::Instance()->FreeConn(sql);
    //如果插入成功，输出日志
    LOG_DEBUG("UserVerify success!!");
    //如果插入成功，返回true
    return flag;
}

std::string HttpRequest::path() const{
    return path_;
}

std::string& HttpRequest::path(){
    return path_;
}
std::string HttpRequest::method() const {
    return method_;
}

std::string HttpRequest::version() const {
    return version_;
}

std::string HttpRequest::GetPost(const std::string& key) const 
{
    assert(key != "");
    if(post_.count(key) == 1)
    {
        return post_.find(key)->second;
    }
    return "";
}

std::string HttpRequest::GetPost(const char* key) const 
{
    assert(key != nullptr);
    if(post_.count(key) == 1)
    {
        return post_.find(key)->second;
    }
    return "";
}